package org.datamining.guha.association;

import java.util.List;
import java.util.ArrayList;
import org.datamining.guha.model.PatternTreeModel;
import org.datamining.guha.model.literal.Literal;
import org.datamining.guha.model.literal.LiteralFactory;
import org.datamining.guha.rule.Item;
import org.datamining.guha.rule.Rule;
import org.datamining.guha.rule.RuleImpl;

import org.apache.commons.logging.*;

/**
 * Abstract association is used for implementation of
 * all specific association types.
 *
 * @author Lukas Vlcek
 */
public abstract class AbstractAssociation {
    
    static int NO_ITEM_USED = -1;
    static int INCREASE_SUCCESS = -2;
    static int CANNOT_BE_INCREASED = -3;
    
    List<Item> antecedents; 
    List<Item> succedents;
    double support;
    double confidence;    
    
    /* friendly variables used by implementation */
    List<Literal> antecedent; // actual antecedent (List of Literals)
    List<Literal> succedent; // actual succedent (List of Literals)
    double ruleSupport; // support of evaluated rule
    double ruleConfidence; // confidence of evaluated rule
    
    Log log;
    
    /**
     * @param minimal support and minimal confidence of association.
     */
    AbstractAssociation(double support, double confidence) {
        this.antecedents = new ArrayList<Item>();
        this.succedents = new ArrayList<Item>();
        this.support = support;
        this.confidence = confidence;
        // log
        this.log = LogFactory.getLog(getClass());
    }
    
    public List<Item> getAntecedents() {
        return antecedents;
    }
    
    public List<Item> getSuccedents() {
        return succedents;
    }
    
    public double getSupport() {
        return support;
    }

    public double getConfidence() {
        return confidence;
    }
    
    /**
     * Specific implementation of rule type.
     */
    abstract void ruleEvalutaion(PatternTreeModel ptm, int numberOfObjects);
    
    /** @return String Short rule description. It is used in toString() method. */
    abstract String associationDescription();
    
    /**
     * Breath-first traversal through hypothesis space.
     *
     * @return List of all valid hypothesis (Rule class) in model.
     * TODO Remove numberOfObjects parameter. This should be property of model itself.
     */
    public List evaluate(PatternTreeModel ptm, int numberOfObjects /*, ModelAdapter adapter*/) {
        
        resetCedent(antecedents);
        //log.debug(fullAntecedent);
        resetCedent(succedents);
        //log.debug(fullSuccedent);
        
        /* ############################################################# */
        
        List<Rule> rulesList = new ArrayList<Rule>();
        
        antecedent = null;
        succedent = null;
        
        antecedent = growCedent(antecedents);
        //log.debug("antecedent: " + antecedent);
        while (antecedent != null) {
            
            succedent = growCedent(succedents);
            //log.debug("succedent: " + succedent);
            while (succedent != null) {
         
                ruleSupport = 0;
                ruleConfidence = 0;
                
                /* ============ rule evaluation ============ */
                ruleEvalutaion(ptm, numberOfObjects);
                /*
                List a = antecedent;
                List a_b = new ArrayList(antecedent);
                a_b.addAll(succedent);
                
                //log.debug("Getting support of " + a);
                long suppA = ptm.getPatternSupport(a);
                //log.debug("Getting support of " + a_b);
                long suppAB = ptm.getPatternSupport(a_b);
                
                // specific FIMPL calculations 
                ruleSupport = (double) suppAB/numberOfObjects;
                ruleConfidence = (double) suppAB/suppA;
                
                log.debug("Testing hypothesis: " + antecedent + " => " + succedent);
                log.debug("supp = " + ruleSupport);
                log.debug("conf = " + ruleConfidence);
                log.debug(" - - - - - - ");
                 */
                /* ========================================= */
                
                if (ruleSupport < support) {
                    //disableSuccedent;
                    //disablePattern; // Antecedent & Succedent = Pattern - we have found border
                } else if (ruleConfidence < confidence) {
                    //??
                } else {
                	rulesList.add(new RuleImpl(antecedent, succedent, ruleSupport, ruleConfidence));
                }
         
                succedent = growCedent(succedents);
                 
                if (succedent == null) {
                    resetCedent(succedents);
                }
            } 
            antecedent = growCedent(antecedents);
        }
        return rulesList;
    }
    
    /**
     * Grow cedent to new combination.
     * @return List of Literals contained in new conbination or null
     * if new combination can't be created.
     */
    private List<Literal> growCedent(List<Item> cedents) {

        if (cedents.size() == 0) {
            return null;
        }
        
        if (newCombination(cedents) != Item.SHIFT_SUCCESS) {
            return null;
        }
        /*
        for (int i = 0; i < cedents.size(); i++) {
            log.debug((Item)cedents.get(i));
        }
         */
        
        // return all Literals where used[i] == true && disabled[i] == false;
        List<Literal> result = new ArrayList<Literal>();
        int cdnts = cedents.size();
        for (int c = 0; c < cdnts; c++) {
            Item item = (Item) cedents.get(c);
            int len = item.getUsedLiterals().length;
            for (int i = 0; i < len; i++) {
                if (item.getUsedLiterals()[i] == true && item.getDisabledLiterals()[i] == false) {
                    result.add(item.getInteravlLiteral(i));
                }
            }
        }
        if (result.isEmpty()) { 
            return null;
        }
        return result;
    }
    
    /**
     * Set new combination in Item.usedLiterals[x].
     * @param List of Items (this is either Antecedent or Succedent)
     * @return int which is result of operation above Item objects.
     *             Thus it can be some of Item.CONSTANTS. All we
     *             need is to test Item.SHIFTSUCCESS
     */
    private int newCombination(List<Item> cedents) {
        
        int position;
        position = lastUsed(cedents);
        if (position == AbstractAssociation.NO_ITEM_USED) {
            int result = useFirstPossibleItem(cedents);
            return result;
        } else {
            Item item = (Item) cedents.get(position);
            int result = item.shiftUsed();
            if (result != Item.SHIFT_SUCCESS) {
                // try to used next Item on the right instead.
                for (int i = position+1; i < cedents.size(); i++) {
                    Item newItem = (Item) cedents.get(i);
                    if (newItem.useFirstPossibleLiteral() != Item.CANNOT_BE_USED) {
                        // set [current] item not used.
                        item.resetUsed();
                        return Item.SHIFT_SUCCESS;
                    }
                }
                // List prefix = all Items in cedents before Item[at position]
                // int res = newSubcombination();
                int res = newSubcombination(cedents, position);
                if (res == Item.SHIFT_SUCCESS) {
                    return Item.SHIFT_SUCCESS;
                }
                // Increase number of used Items
                if (increaseUsedItems(cedents) == INCREASE_SUCCESS) {
                    return Item.SHIFT_SUCCESS;
                }
            }
            return result;
        }
    }
    
    /**
     * Now we know that Item[position] is used. If there is possible to create
     * a new subcombination then Item[position] needs to be set to unused!
     */
    private int newSubcombination(List cedents, int position) {
        //log.debug("position = " + position);
        //log.debug("Item at position:" + cedents.get(position));
        if (position == 0) { return Item.CANNOT_BE_SHIFTED;}
        int prior = (position-1);
        int used = 0;
        for (int i = prior; i >= 0; i--) {
            //log.debug("Getting Item " + i + " used=" + used);
            Item newItem= (Item) cedents.get(i);
            if (newItem.isUsed()) {
                used++;
                //log.debug("used=" + used);
                //log.debug("i=" + i);
                if (newItem.shiftUsed() == Item.SHIFT_SUCCESS) {
                    //log.debug("ShiftSuccess " + newItem);
                    //used--;
                    // now we need use "used" possible Items between cedents[i+1]
                    // and cedents[position] and resert the rest of then.
                    for (int p = i+1; p <= position; p++) {
                        Item item= (Item) cedents.get(p);
                        item.resetUsed();
                        if (used > 0) {
                            if (item.useFirstPossibleLiteral() != Item.CANNOT_BE_USED) {
                                used--;
                                //log.debug("used--=" + used);
                            } else { /*log.debug("Literal" + p + " can't be used!");*/}
                        }
                    }
                    //log.debug("used = " + used);
                    /*
                    for (int x = 0; x < cedents.size(); x++) {
                        Item item = (Item) cedents.get(x);
                        log.debug(item);
                    }
                     */
                    if (used == 0) {
                        /*
                        if (((Item)cedents.get(position)).isUsed()) {
                            log.debug("Item.CANTBESHIFTED!");
                            return Item.CANTBESHIFTED;
                        }
                        Item  item = (Item) cedents.get(position);
                        item.resetUsed();
                         **/
                        return Item.SHIFT_SUCCESS;
                         
                    } else {
                        return Item.CANNOT_BE_SHIFTED;
                    }
                } else {
                    //used++;
                }
            }
        }
        return Item.CANNOT_BE_SHIFTED;
    }
    
    /**
     * Try to use one more Item and use left-most combination
     * of these items.
     * @return int INCREASESUCCESS or CANTBEINCREASED if there
     * can't be used more Items then it is currently used.
     */
    private int increaseUsedItems(List cedents) {
        
        int used = 0;
        int cdnts = cedents.size();
        // used = how many Items is currently used
        for (int i = 0; i < cdnts; i++) {
            Item item = (Item) cedents.get(i);
            //log.debug(item);
            //log.debug("is used? " + item.isUsed());
            if (item.isUsed() == true) {
                used++;
            }
        }
        // if there are [potentialy] unused Items then try to use one more
        used++;
        if (cdnts >= used) {
            for (int i = 0; i < cdnts; i++) {
                Item item = (Item) cedents.get(i);
                item.resetUsed();
                if (used > 0) {
                    if (item.useFirstPossibleLiteral() != Item.CANNOT_BE_USED) {
                        used--;
                    }
                }
            }
        }
        if (used == 0) { return INCREASE_SUCCESS; }
        return CANNOT_BE_INCREASED;
    }
    
    /**
     * Sets first possible Literal from left in first possible Item from left to used.
     * @return Item.SHIFTSUCCESS if success or Item.CANTBEUSED otherwise.
     */
    private int useFirstPossibleItem(List<Item> cedents) {
    	/*
        int cdnts = cedents.size();
        for (int c = 0; c < cdnts; c++) {
            Item item = (Item) cedents.get(c);
            int result = item.useFirstPossibleLiteral();
            if (result != Item.CANTBEUSED) {
                return Item.SHIFTSUCCESS;
            }
        }*/
        for (Item item : cedents) {
        	int result = item.useFirstPossibleLiteral();
            if (result != Item.CANNOT_BE_USED) {
                return Item.SHIFT_SUCCESS;
            }
		}
        return Item.CANNOT_BE_USED;
    }
    
    /**
     * @return int position of right-most Item which
     * is used or NOITEMUSED if no Item is used.
     */
    private int lastUsed(List cedents) {
        int result = AbstractAssociation.NO_ITEM_USED;
        int cdnts = cedents.size();
        for (int c = cdnts-1; c >= 0; c--) {
            Item item = (Item) cedents.get(c);
            int len = item.getUsedLiterals().length;
            for (int i = 0; i < len; i++) {
                if (item.getUsedLiterals()[i] == true) {
                    return c;
                }
            }
        }
        return result;
    }
    
    /**
     * Set both used and disabled boolean arrays
     * of all Items in cedent to false.
     */
    private void resetCedent(List<Item> cedents) {
    	/*
        int len = cedents.size();
        for (int i = 0; i < len; i++) {
            Item item = (Item) cedents.get(i);
            item.resetDisabled();
            item.resetUsed();
        }*/
        for (Item item : cedents) {
        	item.resetDisabled();
            item.resetUsed();
		}
    }
    
    /**
     * @param List of Literals
     * @return List of negated Literals
     */
    List<Literal> getNegatedVersion(List<Literal> literals) {
        int size = literals.size();
        List<Literal> result = new ArrayList<Literal>(size);
        if (size > 0) {
            LiteralFactory factory = LiteralFactory.getInstance();
            for (int i = 0; i < size; i++) {
                Literal lit = literals.get(i);
                result.add(i, factory.getNegatedLitearl(lit));
            }
        }
        return result;
    }
    
    /**
     * Customized toString() method. It is used only for visual testing.
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(associationDescription() + "\n");
        for (int a = 0 ; a < antecedents.size(); a++) {
            sb.append(((Item) antecedents.get(a)).getName() + " ");
        }
        sb.append("=> ");
        for (int s = 0 ; s < succedents.size(); s++) {
            sb.append(((Item)succedents.get(s)).getName() + " ");
        }
        sb.append("\n");
        
        sb.append("Defined thresholds: support >= " + support + " confidence >= " + confidence);
        
        return sb.toString();
    }
}
